/*
 * Copyright (C) 2012 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.example.android.threadsample;

import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Build;
import android.os.Bundle;
import android.support.v4.app.FragmentActivity;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentManager.OnBackStackChangedListener;
import android.support.v4.app.FragmentTransaction;
import android.support.v4.content.LocalBroadcastManager;
import android.util.Log;
import android.view.View;
import android.view.WindowManager;

/**
 * This activity displays Picasa's current featured images. It uses a service running a background thread to download Picasa's
 * "featured image" RSS feed.
 * <p>
 * An IntentHandler is used to communicate between the active Fragment and this activity. This pattern simulates some of the communication
 * used between activities, and allows this activity to make choices of how to manage the fragments.
 */
public class DisplayActivity extends FragmentActivity implements OnBackStackChangedListener
{

	// A handle to the main screen view
	View						mMainView;

	// An instance of the status broadcast receiver
	DownloadStateReceiver		mDownloadStateReceiver;

	// Tracks whether Fragments are displaying side-by-side
	boolean						mSideBySide;

	// Tracks whether navigation should be hidden
	boolean						mHideNavigation;

	// Tracks whether the app is in full-screen mode
	boolean						mFullScreen;

	// Tracks the number of Fragments on the back stack
	int							mPreviousStackCount;

	// Instantiates a new broadcast receiver for handling Fragment state
	private FragmentDisplayer	mFragmentDisplayer	= new FragmentDisplayer();

	// Sets a tag to use in logging
	private static final String	CLASS_TAG			= "DisplayActivity";

	/**
	 * Sets full screen mode on the device, by setting parameters in the current window and View
	 * 
	 * @param fullscreen
	 */
	public void setFullScreen(boolean fullscreen)
	{
		// If full screen is set, sets the fullscreen flag in the Window manager
		getWindow().setFlags(fullscreen ? WindowManager.LayoutParams.FLAG_FULLSCREEN : 0, WindowManager.LayoutParams.FLAG_FULLSCREEN);

		// Sets the global fullscreen flag to the current setting
		mFullScreen = fullscreen;

		// If the platform version is Android 3.0 (Honeycomb) or above
		if (Build.VERSION.SDK_INT >= 11)
		{

			// Sets the View to be "low profile". Status and navigation bar icons will be dimmed
			int flag = fullscreen ? View.SYSTEM_UI_FLAG_LOW_PROFILE : 0;

			// If the platform version is Android 4.0 (ICS) or above
			if (Build.VERSION.SDK_INT >= 14 && fullscreen)
			{

				// Hides all of the navigation icons
				flag |= View.SYSTEM_UI_FLAG_HIDE_NAVIGATION;
			}

			// Applies the settings to the screen View
			mMainView.setSystemUiVisibility(flag);

			// If the user requests a full-screen view, hides the Action Bar.
			if (fullscreen)
			{
				this.getActionBar().hide();
			}
			else
			{
				this.getActionBar().show();
			}
		}
	}

	/*
	 * A callback invoked when the task's back stack changes. This allows the app to
	 * move to the previous state of the Fragment being displayed.
	 *
	 */
	@Override
	public void onBackStackChanged()
	{

		// Gets the previous global stack count
		int previousStackCount = mPreviousStackCount;

		// Gets a FragmentManager instance
		FragmentManager localFragmentManager = getSupportFragmentManager();

		// Sets the current back stack count
		int currentStackCount = localFragmentManager.getBackStackEntryCount();

		// Re-sets the global stack count to be the current count
		mPreviousStackCount = currentStackCount;

		/*
		 * If the current stack count is less than the previous, something was popped off the stack
		 * probably because the user clicked Back.
		 */
		boolean popping = currentStackCount < previousStackCount;
		Log.d(CLASS_TAG, "backstackchanged: popping = " + popping);

		// When going backwards in the back stack, turns off full screen mode.
		if (popping)
		{
			setFullScreen(false);
		}
	}

	/*
	 * This callback is invoked by the system when the Activity is being killed
	 * It saves the full screen status, so it can be restored when the Activity is restored
	 *
	 */
	@Override
	protected void onSaveInstanceState(Bundle outState)
	{
		outState.putBoolean(Constants.EXTENDED_FULLSCREEN, mFullScreen);
		super.onSaveInstanceState(outState);
	}

	/*
	 * This callback is invoked when the Activity is first created. It sets up the Activity's
	 * window and initializes the Fragments associated with the Activity
	 */
	@Override
	public void onCreate(Bundle stateBundle)
	{
		// Sets fullscreen-related flags for the display
		getWindow().setFlags(WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN | WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR,
				WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN | WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR);

		// Calls the super method (required)
		super.onCreate(stateBundle);

		// Inflates the main View, which will be the host View for the fragments
		mMainView = getLayoutInflater().inflate(R.layout.fragmenthost, null);

		// Sets the content view for the Activity
		setContentView(mMainView);

		/*
		 * Creates an intent filter for DownloadStateReceiver that intercepts broadcast Intents
		 */

		// The filter's action is BROADCAST_ACTION
		IntentFilter statusIntentFilter = new IntentFilter(Constants.BROADCAST_ACTION);

		// Sets the filter's category to DEFAULT
		statusIntentFilter.addCategory(Intent.CATEGORY_DEFAULT);

		// Instantiates a new DownloadStateReceiver
		mDownloadStateReceiver = new DownloadStateReceiver();

		// Registers the DownloadStateReceiver and its intent filters
		LocalBroadcastManager.getInstance(this).registerReceiver(mDownloadStateReceiver, statusIntentFilter);

		/*
		 * Creates intent filters for the FragmentDisplayer
		 */

		// One filter is for the action ACTION_VIEW_IMAGE
		IntentFilter displayerIntentFilter = new IntentFilter(Constants.ACTION_VIEW_IMAGE);

		// Adds a data filter for the HTTP scheme
		displayerIntentFilter.addDataScheme("http");

		// Registers the receiver
		LocalBroadcastManager.getInstance(this).registerReceiver(mFragmentDisplayer, displayerIntentFilter);

		// Creates a second filter for ACTION_ZOOM_IMAGE
		displayerIntentFilter = new IntentFilter(Constants.ACTION_ZOOM_IMAGE);

		// Registers the receiver
		LocalBroadcastManager.getInstance(this).registerReceiver(mFragmentDisplayer, displayerIntentFilter);

		// Gets an instance of the support library FragmentManager
		FragmentManager localFragmentManager = getSupportFragmentManager();

		/*
		 * Detects if side-by-side display should be enabled. It's only available on xlarge and
		 * sw600dp devices (for example, tablets). The setting in res/values/ is "false", but this
		 * is overridden in values-xlarge and values-sw600dp.
		 */
		mSideBySide = getResources().getBoolean(R.bool.sideBySide);

		/*
		 * Detects if hiding navigation controls should be enabled. On xlarge andsw600dp, it should
		 * be false, to avoid having the user enter an additional tap.
		 */
		mHideNavigation = getResources().getBoolean(R.bool.hideNavigation);

		/*
		 * Adds the back stack change listener defined in this Activity as the listener for the
		 * FragmentManager. See the method onBackStackChanged().
		 */
		localFragmentManager.addOnBackStackChangedListener(this);

		// If the incoming state of the Activity is null, sets the initial view to be thumbnails
		if (null == stateBundle)
		{

			// Starts a Fragment transaction to track the stack
			FragmentTransaction localFragmentTransaction = localFragmentManager.beginTransaction();

			// Adds the PhotoThumbnailFragment to the host View
			localFragmentTransaction.add(R.id.fragmentHost, new PhotoThumbnailFragment(), Constants.THUMBNAIL_FRAGMENT_TAG);

			// Commits this transaction to display the Fragment
			localFragmentTransaction.commit();

			// The incoming state of the Activity isn't null.
		}
		else
		{

			// Gets the previous state of the fullscreen indicator
			mFullScreen = stateBundle.getBoolean(Constants.EXTENDED_FULLSCREEN);

			// Sets the fullscreen flag to its previous state
			setFullScreen(mFullScreen);

			// Gets the previous backstack entry count.
			mPreviousStackCount = localFragmentManager.getBackStackEntryCount();
		}
	}

	/*
	 * This callback is invoked when the system is about to destroy the Activity.
	 */
	@Override
	public void onDestroy()
	{

		// If the DownloadStateReceiver still exists, unregister it and set it to null
		if (mDownloadStateReceiver != null)
		{
			LocalBroadcastManager.getInstance(this).unregisterReceiver(mDownloadStateReceiver);
			mDownloadStateReceiver = null;
		}

		// Unregisters the FragmentDisplayer instance
		LocalBroadcastManager.getInstance(this).unregisterReceiver(this.mFragmentDisplayer);

		// Sets the main View to null
		mMainView = null;

		// Must always call the super method at the end.
		super.onDestroy();
	}

	/*
	 * This callback is invoked when the system is stopping the Activity. It stops
	 * background threads.
	 */
	@Override
	protected void onStop()
	{

		// Cancel all the running threads managed by the PhotoManager
		PhotoManager.cancelAll();
		super.onStop();
	}

	/**
	 * This class uses the BroadcastReceiver framework to detect and handle status messages from the service that downloads URLs.
	 */
	private class DownloadStateReceiver extends BroadcastReceiver
	{

		private DownloadStateReceiver()
		{

			// prevents instantiation by other packages.
		}

		/**
		 * 
		 * This method is called by the system when a broadcast Intent is matched by this class' intent filters
		 * 
		 * @param context
		 *            An Android context
		 * @param intent
		 *            The incoming broadcast Intent
		 */
		@Override
		public void onReceive(Context context, Intent intent)
		{
			/*
			 * Gets the status from the Intent's extended data, and chooses the appropriate action
			 */
			switch (intent.getIntExtra(Constants.EXTENDED_DATA_STATUS, Constants.STATE_ACTION_COMPLETE))
			{
			// Logs "started" state
				case Constants.STATE_ACTION_STARTED:
					if (Constants.LOGD)
					{
						Log.d(CLASS_TAG, "State: STARTED");
					}
					break;
				// Logs "connecting to network" state
				case Constants.STATE_ACTION_CONNECTING:
					if (Constants.LOGD)
					{
						Log.d(CLASS_TAG, "State: CONNECTING");
					}
					break;
				// Logs "parsing the RSS feed" state
				case Constants.STATE_ACTION_PARSING:
					if (Constants.LOGD)
					{
						Log.d(CLASS_TAG, "State: PARSING");
					}
					break;
				// Logs "Writing the parsed data to the content provider" state
				case Constants.STATE_ACTION_WRITING:
					if (Constants.LOGD)
					{
						Log.d(CLASS_TAG, "State: WRITING");
					}
					break;
				// Starts displaying data when the RSS download is complete
				case Constants.STATE_ACTION_COMPLETE:
					// Logs the status
					if (Constants.LOGD)
					{
						Log.d(CLASS_TAG, "State: COMPLETE");
					}

					// Finds the fragment that displays thumbnails
					PhotoThumbnailFragment localThumbnailFragment = (PhotoThumbnailFragment) getSupportFragmentManager().findFragmentByTag(
							Constants.THUMBNAIL_FRAGMENT_TAG);

					// If the thumbnail Fragment is hidden, don't change its display status
					if ((localThumbnailFragment == null) || (!localThumbnailFragment.isVisible()))
						return;

					// Indicates that the thumbnail Fragment is visible
					localThumbnailFragment.setLoaded(true);
					break;
				default:
					break;
			}
		}
	}

	/**
	 * This class uses the broadcast receiver framework to detect incoming broadcast Intents and change the currently-visible fragment based
	 * on the Intent action. It adds or replaces Fragments as necessary, depending on how much screen real-estate is available.
	 */
	private class FragmentDisplayer extends BroadcastReceiver
	{

		// Default null constructor
		public FragmentDisplayer()
		{
			// Calls the constructor for BroadcastReceiver
			super();
		}

		/**
		 * Receives broadcast Intents for viewing or zooming pictures, and displays the appropriate Fragment.
		 * 
		 * @param context
		 *            The current Context of the callback
		 * @param intent
		 *            The broadcast Intent that triggered the callback
		 */
		@Override
		public void onReceive(Context context, Intent intent)
		{

			// Declares a local FragmentManager instance
			FragmentManager fragmentManager1;

			// Declares a local instance of the Fragment that displays photos
			PhotoFragment photoFragment;

			// Stores a string representation of the URL in the incoming Intent
			String urlString;

			// If the incoming Intent is a request is to view an image
			if (intent.getAction().equals(Constants.ACTION_VIEW_IMAGE))
			{

				// Gets an instance of the support library fragment manager
				fragmentManager1 = getSupportFragmentManager();

				// Gets a handle to the Fragment that displays photos
				photoFragment = (PhotoFragment) fragmentManager1.findFragmentByTag(Constants.PHOTO_FRAGMENT_TAG);

				// Gets the URL of the picture to display
				urlString = intent.getDataString();

				// If the photo Fragment exists from a previous display
				if (null != photoFragment)
				{

					// If the incoming URL is not already being displayed
					if (!urlString.equals(photoFragment.getURLString()))
					{

						// Sets the Fragment to use the URL from the Intent for the photo
						photoFragment.setPhoto(urlString);

						// Loads the photo into the Fragment
						photoFragment.loadPhoto();
					}

					// If the Fragment doesn't already exist
				}
				else
				{
					// Instantiates a new Fragment
					photoFragment = new PhotoFragment();

					// Sets the Fragment to use the URL from the Intent for the photo
					photoFragment.setPhoto(urlString);

					// Starts a new Fragment transaction
					FragmentTransaction localFragmentTransaction2 = fragmentManager1.beginTransaction();

					// If the fragments are side-by-side, adds the photo Fragment to the display
					if (mSideBySide)
					{
						localFragmentTransaction2.add(R.id.fragmentHost, photoFragment, Constants.PHOTO_FRAGMENT_TAG);
						/*
						 * If the Fragments are not side-by-side, replaces the current Fragment with
						 * the photo Fragment
						 */
					}
					else
					{
						localFragmentTransaction2.replace(R.id.fragmentHost, photoFragment, Constants.PHOTO_FRAGMENT_TAG);
					}

					// Don't remember the transaction (sets the Fragment backstack to null)
					localFragmentTransaction2.addToBackStack(null);

					// Commits the transaction
					localFragmentTransaction2.commit();
				}

				// If not in side-by-side mode, sets "full screen", so that no controls are visible
				if (!mSideBySide)
					setFullScreen(true);

				/*
				 * If the incoming Intent is a request to zoom in on an existing image
				 * (Notice that zooming is only supported on large-screen devices)
				 */
			}
			else if (intent.getAction().equals(Constants.ACTION_ZOOM_IMAGE))
			{

				// If the Fragments are being displayed side-by-side
				if (mSideBySide)
				{

					// Gets another instance of the FragmentManager
					FragmentManager localFragmentManager2 = getSupportFragmentManager();

					// Gets a thumbnail Fragment instance
					PhotoThumbnailFragment localThumbnailFragment = (PhotoThumbnailFragment) localFragmentManager2
							.findFragmentByTag(Constants.THUMBNAIL_FRAGMENT_TAG);

					// If the instance exists from a previous display
					if (null != localThumbnailFragment)
					{

						// if the existing instance is visible
						if (localThumbnailFragment.isVisible())
						{

							// Starts a fragment transaction
							FragmentTransaction localFragmentTransaction2 = localFragmentManager2.beginTransaction();

							/*
							 * Hides the current thumbnail, clears the backstack, and commits the
							 * transaction
							 */
							localFragmentTransaction2.hide(localThumbnailFragment);
							localFragmentTransaction2.addToBackStack(null);
							localFragmentTransaction2.commit();

							// If the existing instance is not visible, display it by going "Back"
						}
						else
						{

							// Pops the back stack to show the previous Fragment state
							localFragmentManager2.popBackStack();
						}
					}

					// Removes controls from the screen
					setFullScreen(true);
				}
			}
		}
	}

}
